import numpy as np

class NeuronNetwork:#用于模拟单隐层的神经网络类

    def __init__(self, input: int, hidden: int, output: int):

        # 神经网络的基本信息
        self.input = input
        self.hidden = hidden
        self.output = output

        # 网络参数的随机初始化
        self.V = np.random.rand(input, hidden)#输入层向单隐层的权值矩阵
        self.W = np.random.rand(hidden, output)#单隐层向输出层的权值矩阵
        self.Bias = np.random.rand(1, hidden)#隐层偏移bias
        self.Gamma = np.random.rand(1, output)#输出层偏置gamma

    @staticmethod
    def Sigmoid(x):
        '''
        Sigmoid函数，属于静态类方法.一般来说，要使用某个类的方法，需要先实例化一个对象再调用方法。
        而使用@staticmethod就可以不需要实例化，直接类名.方法名()来调用。
        这有利于组织代码，把某些应该属于某个类的函数给放到那个类里去，同时有利于命名空间的整洁。
        '''
        return 1 / (1 + np.exp(-x))

    def network(self, x):#获取输入向量在中间层和输出端的输出,x为输入矩阵
        #一次network为一次训练
        #print(x)
        #print(self.V)
        hidden = NeuronNetwork.Sigmoid(x @ self.V - self.Bias)#计算隐层
        #print(hidden)
        #print(self.W )
        output = NeuronNetwork.Sigmoid(hidden @ self.W - self.Gamma)#计算输出层
        #print(output)
        # @，用于向量和矩阵的运算
        #print(hidden)
        #print(output)
        return hidden, output

    def error(self,train_x,train_y):#计算均方误差
        hidden, output = self.network(train_x)
        data_len = self.train_x.shape[0]
        return   np.sum((output - train_y)**2)/data_len

    def train(self, train_x, train_y, eta, threshold):
        #train_x输入层
        #train_y真实值，与输出层对应
        self.train_x = np.array(train_x)
        self.train_y = np.array(train_y)
        data_len = self.train_x.shape[0]

        old_error = self.error(self.train_x, self.train_y)#计算均方误差MSE
        while True:
            for k in range(data_len):
                x, y = self.train_x[k], self.train_y[k]
                hidden, output = self.network(x)
                # 获取梯度 g:输出层梯度  e:隐层梯度
                g = output * (1 - output) * (y - output)
                e = hidden * (1 - hidden) * (self.W @ g.T).T #.T转置
                # 更新权值
                self.W += eta * hidden.T @ g
                self.Gamma -= eta * g
                self.V += eta * (e.T @ np.array([self.train_x[k]])).T
                self.Bias -= eta * e

            # 获取更新后误差
            new_error = self.error(self.train_x, self.train_y)
            print("误差更新:",old_error,new_error)
            print("差值：",old_error - new_error)
            # 到达阈值后便停止
            if abs(old_error - new_error) <= threshold:
                break
            else:
                old_error = new_error

    def predict(self, x):
            output = self.network(x)[1]

            return output
'''
            allresult=[]
            for y in self.train_y:
                if y not in allresult:

                          allresult.append(y)

            #print(allresult)
            result=[]
            for op in output:
                res=[]
                for re in allresult:
                    distance=0
                    for i in range(len(op)):
                        distance +=(op[i]-re[i])**2
                    res.append(distance)
                x=allresult[res.index(max(res))]
                if len(x)==1:
                   result.append(x[0])
                else:
                   result.append(x)
            #print(output)
            '''
'''
            c = np.argmax(output)#选择输出层中输出值最大的神经元序数作为类别序数
            print(c)
            prob = output[0][c] / sum(output[0])
            '''